package com.sell.service.impl;

import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.lly835.bestpay.enums.BestPayTypeEnum;
import com.lly835.bestpay.model.PayRequest;
import com.lly835.bestpay.model.PayResponse;
import com.lly835.bestpay.model.RefundRequest;
import com.lly835.bestpay.model.RefundResponse;
import com.lly835.bestpay.service.BestPayService;
import com.lly835.bestpay.utils.JsonUtil;
import com.sell.VO.PayVO;
import com.sell.config.WechatProperties;
import com.sell.dto.OrderDTO;
import com.sell.enums.ResultEnum;
import com.sell.exception.SellException;
import com.sell.service.OrderService;
import com.sell.service.PayService;
import com.sell.utils.DateUtils;
import com.sell.utils.JsonUtils;
import com.sell.utils.MathUtils;
import com.sell.utils.MoneyUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;

/**
 * Author: Xukai
 * Description:
 * CreateDate: 2018/6/26 13:37
 * Modified By:
 */
@Service
@Slf4j
public class PayServiceImpl implements PayService {

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private WechatProperties properties;

    private static final String ORDER_NAME = "Wechat Order";

    @Autowired
    private BestPayService bestPayService;

    // 基于Wechat-Group/weixin-java-tools实现。之所以在这里写了这套实现，原因：
    // 1、bestPay实现比较混乱，各种参数定义前后不统一，很容易产生歧义
    // 2、bestPay在支付方面的实现过于简单，如优惠券的使用、分批退款等很多接口都没有实现
    // ======================================================================================================
    @Override
    public PayVO create(OrderDTO orderDTO, HttpServletRequest request) {
        try {
            WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
            orderRequest.setBody(ORDER_NAME);
            orderRequest.setOutTradeNo(orderDTO.getOrderId());
            orderRequest.setTotalFee(MoneyUtils.yuanToFen(orderDTO.getOrderAmount()));//元转成分
            orderRequest.setOpenid(orderDTO.getBuyerOpenid());
            orderRequest.setSpbillCreateIp(request.getRemoteAddr());
            Date date = new Date();
            orderRequest.setTimeStart(DateUtils.formatDate(date, "yyyyMMddHHmmss"));
            Calendar cal = Calendar.getInstance();
            cal.setTime(date);
            cal.add(Calendar.MINUTE, 30);
            Date dateExpire = cal.getTime();
            orderRequest.setTimeExpire(DateUtils.formatDate(dateExpire, "yyyyMMddHHmmss"));
            log.info("【微信支付】发起支付, request={}", JsonUtils.objectToJson(orderRequest));
            WxPayMpOrderResult wxPayMpOrderResult = wxPayService.createOrder(orderRequest);
            PayVO payVO = new PayVO();
            BeanUtils.copyProperties(wxPayMpOrderResult, payVO);
            return payVO;
        } catch (Exception e) {
            log.error("【微信支付】订单号：orderId = {}", orderDTO.getOrderId());
            throw new SellException(ResultEnum.WX_PAY_ERROR.getCode(), e.getMessage());
        }
    }

    @Override
    public PayVO notify(String notifyData) {
        WxPayOrderNotifyResult notifyResult = null;
        try {
            notifyResult = wxPayService.parseOrderNotifyResult(notifyData);
        } catch (WxPayException e) {
            log.error("");
            e.printStackTrace();
        }
        //1. 验证签名【默认】
        //2. 支付的状态【默认】
        //3. 支付金额【必须实现】
        //4. 支付人(下单人 == 支付人)【可选】
        // 查询订单
        OrderDTO orderDTO = orderService.findOne(notifyResult.getOutTradeNo());
        if (!MathUtils.equals(orderDTO.getOrderAmount().doubleValue(), MoneyUtils.fenToYuan(notifyResult.getTotalFee()), properties.getMoneyDiffScale())) {
            log.error("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}",
                    notifyResult.getOutTradeNo(),
                    MoneyUtils.fenToYuan(notifyResult.getTotalFee()),
                    orderDTO.getOrderAmount());
            throw new SellException(ResultEnum.WX_PAY_NOTIFY_MONEY_VERIFY_ERROR);
        }
        //修改订单的支付状态
        orderService.paid(orderDTO);
        PayVO payVO = new PayVO();
        BeanUtils.copyProperties(notifyResult, payVO);
        return payVO;
    }

    @Override
    public PayVO refund(OrderDTO orderDTO) {
        WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
        wxPayRefundRequest.setOutTradeNo(orderDTO.getOrderId());
        //本业务中退款只能全额退款，所以退款只发生一次，退款单号用订单号即可
        wxPayRefundRequest.setOutRefundNo(orderDTO.getOrderId());
        wxPayRefundRequest.setTotalFee(MoneyUtils.yuanToFen(orderDTO.getOrderAmount()));
        wxPayRefundRequest.setRefundFee(MoneyUtils.yuanToFen(orderDTO.getOrderAmount()));
        WxPayRefundResult wxPayRefundResult = null;
        try {
            wxPayRefundResult = wxPayService.refund(wxPayRefundRequest);
        } catch (WxPayException e) {
            e.printStackTrace();
        }
        PayVO payVO = new PayVO();
        BeanUtils.copyProperties(wxPayRefundResult, payVO);
        return payVO;
    }
    // ======================================================================================================

    // @Override
    public PayVO create1(OrderDTO orderDTO, HttpServletRequest request) {
        PayRequest payRequest = new PayRequest();
        payRequest.setOpenid(orderDTO.getBuyerOpenid());
        payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
        payRequest.setOrderId(orderDTO.getOrderId());
        payRequest.setOrderName(ORDER_NAME);
        payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
        log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest));
        PayResponse payResponse = bestPayService.pay(payRequest);
        log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse));
        PayVO payVO = new PayVO();
        BeanUtils.copyProperties(payResponse, payVO);
        return payVO;
    }

    // @Override
    public PayVO notify1(String notifyData) {
        //1. 验证签名【默认】
        //2. 支付的状态【默认】
        //3. 支付金额【必须实现】
        //4. 支付人(下单人 == 支付人)【可选】
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        // 查询订单
        OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
        if (!MathUtils.equals(orderDTO.getOrderAmount().doubleValue(), payResponse.getOrderAmount(), properties.getMoneyDiffScale())) {
            log.error("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}",
                    payResponse.getOrderId(),
                    payResponse.getOrderAmount(),
                    orderDTO.getOrderAmount());
            throw new SellException(ResultEnum.WX_PAY_NOTIFY_MONEY_VERIFY_ERROR);
        }
        //修改订单的支付状态
        orderService.paid(orderDTO);
        PayVO payVO = new PayVO();
        payVO.setTotalFee(MoneyUtils.yuanToFen(BigDecimal.valueOf(payResponse.getOrderAmount())));
        payVO.setOutTradeNo(payResponse.getOrderId());
        return payVO;
    }

    // @Override
    public PayVO refund1(OrderDTO orderDTO) {
        RefundRequest refundRequest = new RefundRequest();
        refundRequest.setOrderId(orderDTO.getOrderId());
        refundRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
        refundRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
        log.info("【微信退款】request={}", JsonUtil.toJson(refundRequest));
        RefundResponse refundResponse = bestPayService.refund(refundRequest);
        log.info("【微信退款】response={}", JsonUtil.toJson(refundResponse));
        PayVO payVO = new PayVO();
        payVO.setOutTradeNo(refundResponse.getOrderId());
        payVO.setTransactionId(refundResponse.getOutTradeNo());
        payVO.setTotalFee(MoneyUtils.yuanToFen(BigDecimal.valueOf(refundResponse.getOrderAmount())));
        payVO.setRefundFee(MoneyUtils.yuanToFen(BigDecimal.valueOf(refundResponse.getOrderAmount())));
        payVO.setRefundId(refundResponse.getRefundId());
        payVO.setOutRefundNo(refundResponse.getOutRefundNo());
        return payVO;
    }
}
